﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using SilentVoice.Models;
using SilentVoice.Data;
using System.Security.Principal;
using System.Threading;
using System.Security.Permissions;
using SilentVoice.Security;
using System.ComponentModel.DataAnnotations;

namespace SilentVoice.Business
{
    [PrincipalPermission(SecurityAction.Demand, Authenticated=true)]
    public class Manager
    {
        ICollection<ValidationResult> errors;

        private UserManagement userManager;
        private EventManagement eventManager;
        private User loggedInUser;

        public Manager()
        {
            this.userManager = new UserManagement();
            this.eventManager = new EventManagement();

            loggedInUser = ((SVPrincipal)Thread.CurrentPrincipal).User; 
        }

       
        [PrincipalPermission(SecurityAction.Demand, Role=Role.RoleTypes.Faculty.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.AccountOwner.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Administrator.ToString())]
        public User CreateUser(User user)
        {
            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                user.AccountId = loggedInUser.AccountId;
            }

            if (user.IsValid(out errors))
            {
                User createdUser = userManager.CreateUser(user);
                return createdUser;
            }
            else
            {
                throw new ValidationException("User not valid!");
                //TODO: need an aggregate validation exceptions class
            }
            
        }

        public User UpdateUser(User user) //TODO: make sure update doesn't change password
        {
            if (Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.User.ToString()))
            {
                user.Id = loggedInUser.Id;
                user.Email = loggedInUser.Email;
            }

            if (user.IsValid(out errors))
            {
                User updatedUser = userManager.UpdateUser(user);
                return updatedUser;
            }
            else
            {
                throw new ValidationException("User not valid!");
                //TODO: need an aggregate validation exceptions class
            }
        }

        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Faculty.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.AccountOwner.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Administrator.ToString())]
        public User DeleteUser(User user)
        {
            User actualUser = userManager.GetUser(user.Id);
            
            RestrictSameAccount(actualUser.AccountId);

            User deletedUser = userManager.DeleteUser(actualUser);
            
            return deletedUser;
        }

        public Account GetAccount(int id)
        {
            RestrictSameAccount(id);

            Account account = userManager.GetAccount(id);

            if (account == null)
            {
                throw new ArgumentOutOfRangeException(String.Format("Account with id {0} does not exist",id));
            }

            return account;
        }

        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Faculty.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.AccountOwner.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Administrator.ToString())]
        public ICollection<User> GetAccountUsers(int accountId)
        {
            RestrictSameAccount(accountId);

            ICollection<User> users = userManager.GetAccountUsers(accountId);

            return users;
        }

        public Event CreateEvent(Event svEvent)
        {
            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                svEvent.AccountId = loggedInUser.AccountId;
            }

            if (svEvent.IsValid(out errors))
            {
                Event createdEvent = eventManager.CreateEvent(svEvent);
                return createdEvent;
            }
            else
            {
                throw new ValidationException("Event not valid!");
                //TODO: throw validate exception
            }
        }

        public Event UpdateEvent(Event svEvent)
        {
            if (Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.User.ToString()))
            {
                Event actualEvent = eventManager.GetEvent(svEvent.Id);
                if (actualEvent.OwnerId != loggedInUser.Id)
                {
                    //TODO: throw unauthorized exception
                }
            }

            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                Event actualEvent = eventManager.GetEvent(svEvent.Id);
                if (actualEvent.AccountId != loggedInUser.AccountId)
                {
                    //TODO: throw unauthorized exception
                }
            }

            if (svEvent.IsValid(out errors))
            {
                Event updatedEvent = eventManager.UpdateEvent(svEvent);
                return updatedEvent;
            }
            else
            {
                throw new ValidationException("Event not valid!");
                //TODO: throw validate exception
            }
        }

        public Event GetEvent(int id)
        {
            Event svEvent = eventManager.GetEvent(id);

            if (svEvent == null)
            {
                throw new ArgumentOutOfRangeException(String.Format("Event with id {0} does not exist", id));
            }

            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                if (svEvent.AccountId != loggedInUser.AccountId)
                {
                    //TODO: throw unauthorized exception
                }
            }

            return svEvent;
        }

        public Event DeleteEvent(Event svEvent)
        {
            Event actualEvent = eventManager.GetEvent(svEvent.Id);

            if (Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.User.ToString()))
            {
                if (svEvent.OwnerId != loggedInUser.Id)
                {
                    //TODO: throw unauthorized exception
                }
            }

            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                if (svEvent.AccountId != loggedInUser.AccountId)
                {
                    //TODO: throw unauthorized exception
                }
            }

            Event deletedEvent = eventManager.DeleteEvent(svEvent);

            return deletedEvent;
        }
        
        public ScratchPad GetScratchPadForEvent(Event svEvent)
        {
            return GetScratchPadForEvent(loggedInUser, svEvent);
        }

        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Faculty.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.AccountOwner.ToString())]
        [PrincipalPermission(SecurityAction.Demand, Role = Role.RoleTypes.Administrator.ToString())]
        public ScratchPad GetScratchPadForEvent(User owner, Event svEvent)
        {
            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                User actualUser = userManager.GetUser(owner.Id);
                
                if (actualUser.AccountId != loggedInUser.AccountId)
                {
                    //TODO: throw exception
                }

                Event actualEvent = eventManager.GetEvent(svEvent.Id);

                if (actualEvent.AccountId != loggedInUser.AccountId)
                {
                    //TODO: throw exception
                }
            }

            EventManagement userData = new EventManagement();
            ScratchPad scratchPad = userData.GetScratchPadForEvent(owner, svEvent);
            
            if (scratchPad == null)
            {
                throw new ArgumentOutOfRangeException("No ScratchPad exists for this user for this event");//TODO: This exception should be thrown at datalayer level
            }
            return scratchPad;
        }


        private void RestrictSameAccount(int accountId)
        {
            if (!Thread.CurrentPrincipal.IsInRole(Role.RoleTypes.Administrator.ToString()))
            {
                if (loggedInUser.AccountId != accountId)
                {
                    //TODO: throw an exception
                    //TODO: this is a common pattern...factor it out
                }
            }
        }
    }
}
